﻿using Microsoft.VisualStudio.GraphModel;
using Microsoft.VisualStudio.Progression;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Media;

namespace Timtopia.DGMLPosterView
{
    /// <summary>
    /// This is the class that implements the package exposed by this assembly.
    ///
    /// The minimum requirement for a class to be considered a valid package for Visual Studio
    /// is to implement the IVsPackage interface and register itself with the shell.
    /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
    /// to do it: it derives from the Package class that provides the implementation of the 
    /// IVsPackage interface and uses the registration attributes defined in the framework to 
    /// register itself and its components with the shell.
    /// </summary>
    // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is
    // a package.
    [PackageRegistration(UseManagedResourcesOnly = true)]
    // This attribute is used to register the information needed to show this package
    // in the Help/About dialog of Visual Studio.
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    // This attribute is needed to let the shell know that this package exposes some menus.
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [Guid(GuidList.guidPosterViewPkgString)]
    public sealed class PosterViewPackage : Package
    {
        /// <summary>
        /// Default constructor of the package.
        /// Inside this method you can place any initialization code that does not require 
        /// any Visual Studio service because at this point the package object is created but 
        /// not sited yet inside Visual Studio environment. The place to do all the other 
        /// initialization is the Initialize method.
        /// </summary>
        public PosterViewPackage()
        {
            Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
        }

        /////////////////////////////////////////////////////////////////////////////
        // Overridden Package Implementation
        #region Package Members

        /// <summary>
        /// Initialization of the package; this method is called right after the package is sited, so this is the place
        /// where you can put all the initialization code that rely on services provided by VisualStudio.
        /// </summary>
        protected override void Initialize()
        {
            Debug.WriteLine (string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
            base.Initialize();

            VsShell.Provider = (System.IServiceProvider)this;

            // Add our command handlers for menu (commands must exist in the .vsct file)
            OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if ( null != mcs )
            {
                // Create the command for the menu item.
                CommandID menuCommandID = new CommandID(GuidList.guidPosterViewPackageCmdSet, (int)PkgCmdIDList.cmdidGreedyPowerGraph);
                MenuCommand menuItem = new MenuCommand(GreedyPowerGraphMenuItemCallback, menuCommandID);
                mcs.AddCommand(menuItem);

                menuCommandID = new CommandID(GuidList.guidPosterViewPackageCmdSet, (int)PkgCmdIDList.cmdidBeamsearchPowerGraph);
                menuItem = new MenuCommand(BeamsearchPowerGraphMenuItemCallback, menuCommandID);
                mcs.AddCommand(menuItem);

                menuCommandID = new CommandID(GuidList.guidPosterViewPackageCmdSet, (int)PkgCmdIDList.cmdidGenerateGraph);
                menuItem = new MenuCommand(GenerateGraphMenuItemCallback, menuCommandID);
                mcs.AddCommand(menuItem);

                menuCommandID = new CommandID(GuidList.guidPosterViewPackageCmdSet, (int)PkgCmdIDList.cmdidPickRandomPath);
                menuItem = new MenuCommand(PickRandomPathMenuItemCallback, menuCommandID);
                mcs.AddCommand(menuItem);

                menuCommandID = new CommandID(GuidList.guidPosterViewPackageCmdSet, (int)PkgCmdIDList.cmdidRandomlabels);
                menuItem = new MenuCommand(RandomLabelsMenuItemCallback, menuCommandID);
                mcs.AddCommand(menuItem);

                menuCommandID = new CommandID(GuidList.guidPosterViewPackageCmdSet, (int)PkgCmdIDList.cmdidLabelsToIds);
                menuItem = new MenuCommand(LabelsToIDsMenuItemCallback, menuCommandID);
                mcs.AddCommand(menuItem);

                menuCommandID = new CommandID(GuidList.guidPosterViewPackageCmdSet, (int)PkgCmdIDList.cmdidLoadModularGraph);
                menuItem = new MenuCommand(LoadModularGraphMenuItemCallback, menuCommandID);
                mcs.AddCommand(menuItem);
            }
        }
        #endregion

        private void GenerateGraphMenuItemCallback(object sender, EventArgs e)
        {
            ModularGraphGenerationController.CreateGraph(this, RandomModularGraphGenerator.Generate);
        }

        private void LoadModularGraphMenuItemCallback(object sender, EventArgs e)
        {
            OpenFileDialog dialog = new OpenFileDialog();
            //dialog.DefaultExt = ".dzn";
            if (dialog.ShowDialog().Value)
            {
                ModularGraphGenerationController.CreateGraph(this, ()=>PowerGraphDecomposition.LoadSimpleDescription(dialog.FileName));
            }
        }

        private void SetCustomVisualFactoryMenuItemCallback(object sender, EventArgs e)
        {
            var window = GetGraphWindow();
            if (window != null)
            {
                ModularGraphGenerationController.SetCustomVisualFactory(window);
            }
        }

        /// <summary>
        /// This function is the callback used to execute a command when the a menu item is clicked.
        /// See the Initialize method to see how the menu item is associated to this function using
        /// the OleMenuCommandService service and the MenuCommand class.
        /// </summary>
        private void GreedyPowerGraphMenuItemCallback(object sender, EventArgs e)
        {
            Graph graph = GetGraph();
            if (graph != null)
            {
                ModularGraphGenerationController.CreateGraph(this,
                    () => PowerGraphDecomposition.CreateDecomposition(graph));
            }
        }

        private void BeamsearchPowerGraphMenuItemCallback(object sender, EventArgs e)
        {
            Graph graph = GetGraph();
            if (graph != null)
            {
                ModularGraphGenerationController.CreateGraph(this,
                    () => PowerGraphDecomposition.CreateBeamsearchDecomposition(graph));
            }
        }

        private void LabelsToIDsMenuItemCallback(object sender, EventArgs e)
        {
            Graph graph = GetGraph();
            if (graph != null)
            {
                using (var scope = new GraphTransactionScope())
                {
                    var leaves = graph.Nodes.Where(v => !v.IsGroup).ToList();
                    leaves.ForEach(v => v.Label = v.Id + "");
                    scope.Complete();
                }
            }
        }
        private void RandomLabelsMenuItemCallback(object sender, EventArgs e)
        {
            Graph graph = GetGraph();
            if (graph != null)
            {
                using (var scope = new GraphTransactionScope())
                {
                    Random r = new Random();
                    var leaves = graph.Nodes.Where(v => !v.IsGroup).ToList(); 
                    var az = Enumerable.Range('a', 'z' - 'a' + 1).Select(i => (Char)i).ToList();
                    leaves.ForEach(v =>
                    {
                        int i = r.Next(az.Count);
                        v.Label = "" + (Char)az[i];
                        az.RemoveAt(i);
                    });
                    scope.Complete();
                }
            }
        }

        static BrushConverter bc = new BrushConverter();
        static SolidColorBrush startNodeBrush = (SolidColorBrush)bc.ConvertFrom("#FFACEA84");
        static SolidColorBrush endNodeBrush = (SolidColorBrush)bc.ConvertFrom("#FFF7B192");

        private void PickRandomPathMenuItemCallback(object sender, EventArgs e)
        {
            Graph graph = GetGraph();
            if (graph != null)
            {
                int pathLength;
                using (var scope = new GraphTransactionScope())
                {
                    List<GraphNode> V;
                    var dist = ShortestPaths.FloydWarshall(graph, out V);
                    var hist = new Dictionary<int, List<Tuple<int, int>>>();
                    for (int i = 0; i < V.Count; ++i)
                    {
                        for (int j = 0; j < V.Count; ++j)
                        {
                            int d = dist[i, j];
                            if (d >= 1)
                            {
                                List<Tuple<int, int>> pairs;
                                if (!hist.TryGetValue(d, out pairs)) {
                                    pairs = new List<Tuple<int,int>>();
                                    hist[d] = pairs;
                                }
                                pairs.Add(Tuple.Create(i,j));
                            }
                        }
                    }
                    pathLength = hist.Keys.Max();
                    var longestPaths = hist[pathLength];

                    Random r = new Random();
                    var path = longestPaths[r.Next(longestPaths.Count)];
                    foreach (var x in V)
                    {
                        x.RemoveCategory(ModularDecomposition.StartNodeCategory);
                        x.RemoveCategory(ModularDecomposition.EndNodeCategory);
                    }
                    GraphNode u = V[path.Item1];
                    GraphNode v = V[path.Item2];
                    u.AddCategory(ModularDecomposition.StartNodeCategory);
                    v.AddCategory(ModularDecomposition.EndNodeCategory);
                    List<GraphLink> linksOnPath;
                    ShortestPaths.FromTo(u, v, out linksOnPath);
                    foreach (var l in graph.Links)
                    {
                        l.RemoveCategory(ModularDecomposition.ShortestPathLinkCategory);
                    }
                    foreach (var l in linksOnPath)
                    {
                        l.AddCategory(ModularDecomposition.ShortestPathLinkCategory);
                    }
                    v = graph.Nodes.GetOrCreate("PathLength");
                    v.Label = "Shortest Path Length = " + pathLength;
                    v.SetStroke(Brushes.Transparent);
                    scope.Complete();
                }
            }
        }

        private Graph GetGraph()
        {
            return GetGraphWindow().Graph;
        }

        private IGraphDocumentWindowPane GetGraphWindow()
        {
            IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
            Guid clsid = Guid.Empty;

            var manager = VsShell.Provider.GetService(typeof(IGraphDocumentManager)) as IGraphDocumentManager;
            var graphWindowPane = manager.MostRecentlyActivated;
            if (graphWindowPane == null)
            {
                int result;
                Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(uiShell.ShowMessageBox(
                           0,
                           ref clsid,
                           "PosterViewPackage",
                           string.Format(CultureInfo.CurrentCulture, "No graph window available... please generate or load a DGML graph", this.ToString()),
                           string.Empty,
                           0,
                           OLEMSGBUTTON.OLEMSGBUTTON_OK,
                           OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
                           OLEMSGICON.OLEMSGICON_INFO,
                           0,        // false
                           out result));
                return null;
            }
            return graphWindowPane;
        }
    }
}
